<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Nibbles</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="GUI, Nibbles, QtJambi, Java">
<meta name="description" content="Nibbles">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Nibbles</h1>

<p>
In this part of the QtJambi programming tutorial, we will create a Nibbles game clone.
</p>

<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div> 

<p>
<b>Nibbles</b> is an older classic video game. It was first created in late 70s.
Later it was brought to PCs. In this game the player controls a snake.
The objective is to eat as many apples as possible. Each time the snake eats an apple,
its body grows. The snake must avoid the walls and its own body. 
</p>


<h2>Development</h2>

<p>
The size of each of the joints of a snake is 10px. The snake is controlled with 
the cursor keys. Initially, the snake has three joints. The game starts immediately. 
When the game is finished, we display "Game Over" message in the center of the window.
</p>

<div class="codehead">Board.java</div>
<pre class="code">
package com.zetcode;

import com.trolltech.qt.core.QBasicTimer;
import com.trolltech.qt.core.QPoint;
import com.trolltech.qt.core.QTimerEvent;
import com.trolltech.qt.core.Qt;
import com.trolltech.qt.gui.QColor;
import com.trolltech.qt.gui.QFont;
import com.trolltech.qt.gui.QFontMetrics;
import com.trolltech.qt.gui.QFrame;
import com.trolltech.qt.gui.QImage;
import com.trolltech.qt.gui.QKeyEvent;
import com.trolltech.qt.gui.QPaintEvent;
import com.trolltech.qt.gui.QPainter;

public class Board extends QFrame {

    private final int WIDTH = 300;
    private final int HEIGHT = 300;
    private final int DOT_SIZE = 10;
    private final int ALL_DOTS = 900;
    private final int RAND_POS = 29;
    private final int DELAY = 140;

    private int x[] = new int[ALL_DOTS];
    private int y[] = new int[ALL_DOTS];

    private int dots;
    private int apple_x;
    private int apple_y;

    private boolean left = false;
    private boolean right = true;

    private boolean up = false;
    private boolean down = false;
    private boolean inGame = true;

    private QBasicTimer timer;
    private QImage ball;
    private QImage apple;
    private QImage head;


    public Board() {
        
        setStyleSheet("QWidget { background-color: black }");

        setFocusPolicy(Qt.FocusPolicy.StrongFocus);

        ball = new QImage("dot.png");
        apple = new QImage("apple.png");
        head = new QImage("head.png");
        
        initGame();
    }


    private void initGame() {

        dots = 3;

        for (int z = 0; z < dots; z++) {
            x[z] = 50 - z*10;
            y[z] = 50;
        }

        locateApple();

        timer = new QBasicTimer();
        timer.start(DELAY, this);
    }


    @Override
    public void paintEvent(QPaintEvent event) {
        super.paintEvent(event);

        QPainter painter = new QPainter();
        painter.begin(this);

        if (inGame) {
            drawObjects(painter);
        } else {
            gameOver(painter);
        }

        painter.end();
    }

    private void drawObjects(QPainter painter) {

        painter.drawImage(apple_x, apple_y, apple);

        for (int z = 0; z < dots; z++) {
            if (z == 0)
                painter.drawImage(x[z], y[z], head);
            else painter.drawImage(x[z], y[z], ball);
        }
    }

    private void gameOver(QPainter painter) {
        String msg = "Game Over";
        QFont small = new QFont("Helvetica", 12,
            QFont.Weight.Bold.value());
        QFontMetrics metr = new QFontMetrics(small);
        

        int textWidth = metr.width(msg);
        int h = height();
        int w = width();

        painter.setPen(QColor.white);
        painter.setFont(small);
        painter.translate(new QPoint(w/2, h/2));
        painter.drawText(-textWidth/2, 0, msg);
    }


    private void checkApple() {

        if ((x[0] == apple_x) &amp;&amp; (y[0] == apple_y)) {
            dots++;
            locateApple();
        }
    }


    private void move() {

        for (int z = dots; z > 0; z--) {
            x[z] = x[(z - 1)];
            y[z] = y[(z - 1)];
        }

        if (left) {
            x[0] -= DOT_SIZE;
        }

        if (right) {
            x[0] += DOT_SIZE;
        }

        if (up) {
            y[0] -= DOT_SIZE;
        }

        if (down) {
            y[0] += DOT_SIZE;
        }
    }


    private void checkCollision() {

        for (int z = dots; z > 0; z--) {

            if ((z > 4) &amp;&amp; (x[0] == x[z]) &amp;&amp; (y[0] == y[z])) {
                inGame = false;
            }
        }

        if (y[0] > HEIGHT) {
            inGame = false;
        }

        if (y[0] < 0) {
            inGame = false;
        }

        if (x[0] > WIDTH) {
            inGame = false;
        }

        if (x[0] < 0) {
            inGame = false;
        }
    }

    private void locateApple() {
        int r = (int) (Math.random() * RAND_POS);
        apple_x = ((r * DOT_SIZE));
        r = (int) (Math.random() * RAND_POS);
        apple_y = ((r * DOT_SIZE));
    }


    @Override
    protected void timerEvent(QTimerEvent event) {

        if (inGame) {
            checkApple();
            checkCollision();
            move();
        } else {
            timer.stop();
        }

        repaint();
    }

    @Override
    public void keyPressEvent(QKeyEvent event)
    {
        
        int key = event.key();
        
        if (key == Qt.Key.Key_Left.value() &amp;&amp; !right) {
            left = true;
            up = false;
            down = false;
        }
        
        if ((key == Qt.Key.Key_Right.value()) &amp;&amp; !left) {
            right = true;
            up = false;
            down = false;            
        }
        
        if ((key == Qt.Key.Key_Up.value()) &amp;&amp; !down) {
            up = true;
            right = false;
            left = false;            
        }
        
        if ((key == Qt.Key.Key_Down.value()) &amp;&amp; !up) {
            down = true;
            right = false;
            left = false;       
        }
    }
}
</pre>

<p>
First we will define some globals used in our game. 
</p>

<p>
The <b class="keyword">WIDTH</b> and <b class="keyword">HEIGHT</b> constants determine 
the size of the Board. The <b class="keyword">DOT_SIZE</b> is the size of the apple and the dot
of the snake. The <b class="keyword">ALL_DOTS</b> constant defines the maximum number of 
possible dots on the Board.
The <b class="keyword">RAND_POS</b> constant is used to calculate a random position of an apple. 
The <b class="keyword">DELAY</b> constant determines the speed of the game.
</p>

<pre class="explanation">
private int x[] = new int[ALL_DOTS];
private int y[] = new int[ALL_DOTS];
</pre>

<p>
These two arrays store x, y coordinates of all possible joints of a snake. 
</p>

<p>
The <b class="keyword">initGame()</b> method initializes variables, loads 
images and starts a timeout function.
</p>

<pre class="explanation">
if (inGame) {
    drawObjects(painter);
} else {
    gameOver(painter);
}
</pre>

<p>
Inside the <b class="keyword">paintEvent()</b> method, we check the <b class="keyword">inGame</b>
variable. If it is true, we draw our objects. The apple and the snake joints. Otherwise we display "Game over"
text.  
</p>

<pre class="explanation">
private void drawObjects(QPainter painter) {

    painter.drawImage(apple_x, apple_y, apple);

    for (int z = 0; z < dots; z++) {
        if (z == 0)
            painter.drawImage(x[z], y[z], head);
        else painter.drawImage(x[z], y[z], ball);
    }
}
</pre>

<p>
The <b class="keyword">drawObjects()</b> method draws the apple and the joints of the
snake. The first joint of a snake is its head, which is represented by a red circle. 
</p>

<pre class="explanation">
private void checkApple() {

    if ((x[0] == apple_x) &amp;&amp; (y[0] == apple_y)) {
        dots++;
        locateApple();
    }
}
</pre>

<p>
The <b class="keyword">checkApple()</b> method checks, if the snake has hit
the apple object. If so, we add another snake joint and call the <b class="keyword">locateApple()</b> 
method, which randomly places a new apple object. 
</p>

<p>
In the <b class="keyword">move()</b> method we have the key algorithm of the game. 
To understand it, look at how the snake is moving. You control the head of the snake. 
You can change its direction with the cursor keys. The rest of the joints move
one position up the chain. The second joint moves where the first was, 
the third joint where the second was etc. 
</p>

<pre class="explanation">
for (int z = dots; z > 0; z--) {
    x[z] = x[(z - 1)];
    y[z] = y[(z - 1)];
}
</pre>

<p>
This code moves the joints up the chain. 
</p>

<pre class="explanation">
if (left) {
    x[0] -= DOT_SIZE;
}
</pre>

<p>
Move the head to the left.
</p>

<p>
In the <b class="keyword">checkCollision()</b> method, we determine if the snake
has hit itself or one of the walls. 
</p>


<pre class="explanation">
for (int z = dots; z > 0; z--) {

    if ((z > 4) &amp;&amp; (x[0] == x[z]) &amp;&amp; (y[0] == y[z])) {
        inGame = false;
    }
}
</pre>

<p>
Finish the game, if the snake hits one of its joints with the head.
</p>

<pre class="explanation">
if (y[0] > HEIGHT) {
    inGame = false;
}
</pre>

<p>
Finish the game, if the snake hits the bottom of the Board. 
</p>

<p>
The <b class="keyword">locateApple()</b> method locates an apple randomly 
on the board.
</p>

<pre class="explanation">
int r = (int) (Math.random() * RAND_POS);
</pre>

<p>
We get a random number from 0 to RAND_POS - 1. 
</p>

<pre class="explanation">
apple_x = ((r * DOT_SIZE));
...
apple_y = ((r * DOT_SIZE));
</pre>

<p>
These line set the x, y coordinates of the apple
object. 
</p>

<pre class="explanation">
if (inGame) {
    checkApple();
    checkCollision();
    move();
} else {
    timer.stop();
}
</pre>

<p>
Every 140 ms, the <b class="keyword">timerEvent()</b> method is called. If 
we are in the game, we call three methods, that build the logic of the game.
Otherwise we stop the timer. 
</p>


<p>
In the <b class="keyword">onKeyPressEvent()</b> method of the Board class, we 
determine the keys that were pressed.
</p>

<pre class="explanation">
if (key == Qt.Key.Key_Left.value() &amp;&amp; !right) {
    left = true;
    up = false;
    down = false;
}
</pre>

<p>
If we hit the left cursor key, we set <b class="keyword">left</b> variable to 
true. This variable is used in the <b class="keyword">move()</b>
method to change coordinates of the snake object. Notice also, that
when the snake is heading to the right, we cannot turn immediately 
to the left. 
</p>

<div class="codehead">Nibbles.java</div>
<pre class="code">
package com.zetcode;

import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QMainWindow;

/**
 * ZetCode QtJambi tutorial
 *
 * In this program, we create
 * a Nibbles game clone.
 *
 * @author jan bodnar
 * website zetcode.com
 * last modified April 2009
 */

public class Nibbles extends QMainWindow {
    
    public Nibbles() {

        setWindowTitle("Nibbles");
    
        resize(310, 310);

        setCentralWidget(new Board());

        move(300, 300);
        show();
    }
    
        
    public static void main(String[] args) {
        QApplication.initialize(args);
        new Nibbles();
        QApplication.exec();
    }
}
</pre>

<p>
In this class, we set up the Nibbles game. 
</p>


<br>
<img src="/img/gui/qtjambi/nibbles.png" alt="Nibbles">
<div class="figure">Figure: Nibbles</div>

<hr class="btm">

<p>
This was the Nibbles computer game programmed with the QtJambi library.
</p>


<br>
<div class="center"> 
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script> 
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> 
</script> 
</div> 
<br>


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡ 
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified April 28, 2009  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- content -->

</body> 
</html>
